#include <stdio.h>
#include <stdlib.h>
#include "../../VEX/pub/libvex.h"

Bool return_false(void*cb, Addr ad)
{
   return False;
}
UInt return_0(void *cb, VexRegisterUpdates* pxControl,
              const VexGuestExtents *vge)
{
   return 0;
}

__attribute__ ((noreturn))
static void failure_exit()
{
   fflush(stdout);
   fprintf(stderr, "//// failure exit called by libVEX\n");
   exit(1);
}

__attribute__ ((noreturn))
static void failure_dispcalled()
{
   fflush(stdout);
   fprintf(stderr, "//// unexpected call to a disp function by libVEX\n");
   exit(1);
}

static void log_bytes (const HChar* chars, SizeT nbytes )
{
   printf("%*s", (int)nbytes, chars);
}

static VexEndness arch_endness (VexArch va) {
   switch (va) {
   case VexArch_INVALID: failure_exit();
   case VexArchX86:    return VexEndnessLE;
   case VexArchAMD64:  return VexEndnessLE;
   case VexArchARM:    return VexEndnessLE;
   case VexArchARM64:  return VexEndnessLE;
   case VexArchPPC32:  return VexEndnessBE;
   case VexArchPPC64:  return VexEndnessBE;
   case VexArchS390X:  return VexEndnessBE;
   case VexArchMIPS32: return VexEndnessBE;
   case VexArchMIPS64: return VexEndnessBE;
   case VexArchTILEGX: return VexEndnessLE;
   default: failure_exit();
   }
}

/* returns whatever kind of hwcaps needed to make
   the host and/or guest VexArch happy. */
static UInt arch_hwcaps (VexArch va) {
   switch (va) {
   case VexArch_INVALID: failure_exit();
   case VexArchX86:    return 0;
   case VexArchAMD64:  return 0;
   case VexArchARM:    return 7;
   case VexArchARM64:  return 0;
   case VexArchPPC32:  return 0;
   case VexArchPPC64:  return 0;
   case VexArchS390X:  return VEX_HWCAPS_S390X_LDISP;
   case VexArchMIPS32: return 0;
   case VexArchMIPS64: return 0;
   case VexArchTILEGX: return 0;
   default: failure_exit();
   }
}

static Bool mode64 (VexArch va) {
   switch (va) {
   case VexArch_INVALID: failure_exit();
   case VexArchX86:    return False;
   case VexArchAMD64:  return True;
   case VexArchARM:    return False;
   case VexArchARM64:  return True;
   case VexArchPPC32:  return False;
   case VexArchPPC64:  return True;
   case VexArchS390X:  return True;
   case VexArchMIPS32: return False;
   case VexArchMIPS64: return True;
   case VexArchTILEGX: return True;
   default: failure_exit();
   }
}

// noinline, as this function is also the one we decode.
__attribute__((noinline)) void get_guest_arch(VexArch    *ga)
{
#if defined(VGA_x86)
   *ga = VexArchX86;
#elif defined(VGA_amd64)
   *ga = VexArchAMD64;
#elif defined(VGA_arm)
   *ga = VexArchARM;
#elif defined(VGA_arm64)
   *ga = VexArchARM64;
#elif defined(VGA_ppc32)
   *ga = VexArchPPC32;
#elif defined(VGA_ppc64be) || defined(VGA_ppc64le) 
   *ga = VexArchPPC64;
#elif defined(VGA_s390x)
   *ga = VexArchS390X;
#elif defined(VGA_mips32)
   *ga = VexArchMIPS32;
#elif defined(VGA_mips64)
   *ga = VexArchMIPS64;
#elif defined(VGA_tilegx)
   *ga = VexArchTILEGX;
#else
   missing arch;
#endif
}

static void show_vta(char *msg, VexTranslateArgs *vta)
{
   printf("//// %s translating guest %s(%d) %s %dbits to host %s(%d)"
          " %s %dbits\n",
          msg,
          LibVEX_ppVexArch(vta->arch_guest),
          vta->arch_guest,
          LibVEX_ppVexEndness(arch_endness(vta->arch_guest)),
          mode64(vta->arch_guest) ? 64 : 32,
          LibVEX_ppVexArch(vta->arch_host),
          vta->arch_host,
          LibVEX_ppVexEndness(arch_endness(vta->arch_host)),
          mode64(vta->arch_host) ? 64 : 32);
}


int main(int argc, char **argv)
{
   const int multiarch = argc > 1 ? atoi(argv[1]) : 0;
   // 0 means: do not do multiarch
   // > 0 means: do multiarch
   // > VexArch_INVALID means: do multiarch, only and specifically
   // with the host arch  equal to multiarch
   // (ugly interface, but hey, that is for testing only special cases only).
   const int endness_may_differ = argc > 2 ? atoi(argv[2]) : 0;
   const int wordsize_may_differ = argc > 3 ? atoi(argv[3]) : 0;
   // Note: if multiarch > VexArch_INVALID, then endness_may_differ
   // and wordsize_may_differ are ignored.

   // So, here are examples of usage:
   //  * run only host == guest:
   //     ./libvexmultiarch_test
   //     ./libvex_test
   //  * run all combinations (this will abort very soon :):
   //     ./libvexmultiarch_test 1 1 1
   //  * run all combinations that are supposed to  work by default :
   //     ./libvexmultiarch_test 1 0 0
   //  * run a specific host arch (e.g. 1028 i.e. VexArchARM64)
   //     ./libvexmultiarch_test 1028
   //  * show how a single arch VEX lib reports its failure when host != guest
   //     ./libvex_test 1 0 0
   

   VexArch guest_arch;
   VexEndness guest_endness;

   VexControl vcon;

   VexGuestExtents  vge;
   VexTranslateArgs vta;
   VexTranslateResult vtr;

   UChar host_bytes[10000];
   Int   host_bytes_used;

   LibVEX_default_VexControl(&vcon);
   LibVEX_Init (failure_exit, log_bytes, 3, &vcon);

   get_guest_arch (&guest_arch);
   guest_endness = arch_endness (guest_arch);
   
   LibVEX_default_VexArchInfo(&vta.archinfo_guest);
   LibVEX_default_VexArchInfo(&vta.archinfo_host);
   LibVEX_default_VexAbiInfo (&vta.abiinfo_both);

   // Use some values that makes AMD64 happy.
   vta.abiinfo_both.guest_stack_redzone_size = 128;

   // Prepare first for a translation where guest == host
   // We will translate the get_guest_arch function
   vta.arch_guest                 = guest_arch;
   vta.archinfo_guest.endness     = guest_endness;
   vta.archinfo_guest.hwcaps      = arch_hwcaps (vta.arch_guest);
   vta.arch_host                  = guest_arch;
   vta.archinfo_host.endness      = guest_endness;
   vta.archinfo_host.hwcaps       = arch_hwcaps (vta.arch_host);
   vta.callback_opaque            = NULL;
   vta.guest_bytes                = (UChar*) get_guest_arch;
   vta.guest_bytes_addr           = (Addr) get_guest_arch;
   vta.chase_into_ok              = return_false;
   vta.guest_extents              = &vge;
   vta.host_bytes                 = host_bytes;
   vta.host_bytes_size            = sizeof host_bytes;
   vta.host_bytes_used            = &host_bytes_used;
   vta.instrument1                = NULL;
   vta.instrument2                = NULL;
   vta.finaltidy                  = NULL;
   vta.needs_self_check           = return_0;
   vta.preamble_function          = NULL;
   vta.traceflags                 = 0xFFFFFFFF;
   vta.sigill_diag                = False;
   vta.addProfInc                 = False;
   vta.disp_cp_chain_me_to_slowEP = failure_dispcalled;
   vta.disp_cp_chain_me_to_fastEP = failure_dispcalled;
   vta.disp_cp_xindir             = failure_dispcalled;
   vta.disp_cp_xassisted          = failure_dispcalled;

   
   show_vta("host == guest", &vta);
   vtr = LibVEX_Translate ( &vta );
   if (vtr.status != VexTransOK) 
      return 1;

   // Now, try various combinations, if told to do so:
   //   host            != guest, 
   //   endness(host)   != endness(guest)     (not well supported)
   //   wordsize (host) != wordsize (guest)   (not well supported)
   // The not well supported combinations are not run, unless requested
   // explicitely via command line arguments.
   if (multiarch) {
      VexArch va;
      for (va = VexArchX86; va <= VexArchTILEGX; va++) {
         vta.arch_host = va;
         vta.archinfo_host.endness = arch_endness (vta.arch_host);
         vta.archinfo_host.hwcaps = arch_hwcaps (vta.arch_host);
         if (arch_endness(va) != arch_endness(guest_arch) 
             && !endness_may_differ
             && multiarch != va) {
            show_vta("skipped (endness differs)", &vta);
            continue;
         }
         if (mode64(va) != mode64(guest_arch) 
             && !wordsize_may_differ
             && multiarch != va) {
            show_vta("skipped (word size differs)", &vta);
            continue;
         }
         if (multiarch > VexArch_INVALID
             && multiarch != va) {
            show_vta("skipped (!= specific requested arch)", &vta);
            continue;
         }
         show_vta ("doing", &vta);
         vtr = LibVEX_Translate ( &vta );
         if (vtr.status != VexTransOK) 
            return 1;
      }
   }

   printf ("//// libvex testing normal exit\n");
   return 0;
}
